今天繼續介紹 trpc api
的部分~終於到第三天開始介紹主題了撒花!!!,今天主要教學 api
的封裝與 context 的共用。
RPC
架構中優一個很重要的概念就 procedures
,procedures
主要負責 api
的 request
到 response
的所有程序,同時他可以幫你處理不管是 middleware
或是 request authorization
等等,而每個 procedures
在 trpc
中都會先初始化一個 context
。
//t1 就是一個 procedures
const t1 = initTRPC.context().create();
// t2 就是另外一個 procedures
const t2 = initTRPC.context().create();
首先你需要定義 api router
,在 t3 stack
中會是在 api route
中去呼叫 trpc
,所以首先你需要先創建一個 handler
如下。
// /pages/api/trpc/[trpc]
import { createNextApiHandler } from "@trpc/server/adapters/next";
import { env } from "~/env.mjs";
import { appRouter } from "~/server/api/root";
import { createTRPCContext } from "~/server/api/trpc";
// export API handler
export default createNextApiHandler({
router: appRouter,
createContext: createTRPCContext,
onError:
env.NODE_ENV === "development"
? ({ path, error }) => {
console.error(
`❌ tRPC failed on ${path ?? "<no-path>"}: ${error.message}`
);
}
: undefined,
});
那 appRouter
跟 createTRPCContext
怎麼來的呢?沒關係我們往下走起
定義好 context
後,指定一個 public
的 procedure
日後可以重用。
const t = initTRPC.context().create({})
const publicProcedure = t.procedure;
createTRPCRouter
就是定義 route
邏輯的地方,可以想成 api
的 handler
,這邊如果寫過 graphql
的話一定很熟悉, trpc
跟 graphql
一樣都是透過 query
去 return value
喔~
import { z } from "zod";
const exampleRouter = createTRPCRouter({
hello: publicProcedure
.input(z.object({ text: z.string() }))
.query(({ input }) => {
return {
greeting: `Hello ${input.text}`,
};
})
})
hello
: api route 的名稱。input
: request 的 query 。query
: 這邊的 callback function 可以吃到 你定義 input 得結果。
在 trpc
中跟 graphql
一樣是透過 mutate
去管理 api
的 post
、delete
、put
const posts = []
const exampleRouter = createTRPCRouter({
hello: publicProcedure
.input(z.object({ text: z.string() }))
.query(({ input }) => {
return {
greeting: `Hello ${input.text}`,
};
}),
add: publicProcedure
.input(z.string())
.mutation(({input}) => {
posts.push(opts.input);
return posts;
}),
})
這邊透過 posts 當作 db 資料
// 定義所有的 api route
export const appRouter = createTRPCRouter({
example: exampleRouter,
});
context
可以在 appRouter
中所有的 router
可以呼叫 context
內容在 query function
中。
import { type CreateNextContextOptions } from "@trpc/server/adapters/next";
import { PrismaClient } from "@prisma/client";
type CreateContextOptions = {
req: NextApiRequest,
res: NextApiResponse
};
const prisma = new PrismaClient()
const createInnerTRPCContext = (opts: CreateContextOptions) => {
return {
prisma,
res: opts.res,
req: opts.req
};
};
const createTRPCContext = async (opts: CreateNextContextOptions) => {
const { req, res } = opts;
// Get the session from the server using the getServerSession wrapper function
return createInnerTRPCContext({
req,
res
});
};
以 hello
這個 route
來說這裡的 ctx
就是透過 createTRPCContext
中 return
的 value
,那因為會使用 prisma
這個框架,就會把他放到 context
中,這樣之後所有的 procedure
,都可以共用同一個 prisma instance
,所以如果你希望用到其他的 ORM
框架的話只要放到 createTRPCContext
中就可以瞜~
hello: publicProcedure
.input(z.object({ text: z.string() }))
.query(({ input, ctx }) => {
const { req, res, prisma } = ctx
return {
greeting: `Hello ${input.text}`,
};
}),
完整的 demo
// 新增一個新的 trpc context
const t = initTRPC.context<typeof createTRPCContext>().create({})
const publicProcedure = t.procedure;
// 定義 api route
const exampleRouter = createTRPCRouter({
hello: publicProcedure
.input(z.object({ text: z.string() }))
.query(({ input }) => {
return {
greeting: `Hello ${input.text}`,
};
})
})
export const appRouter = createTRPCRouter({
example: exampleRouter,
});
export type AppRouter = typeof appRouter;
// ~/server/api/trpc
export default createNextApiHandler({
router: appRouter,
createContext: createTRPCContext,
onError:
env.NODE_ENV === "development"
? ({ path, error }) => {
console.error(
`❌ tRPC failed on ${path ?? "<no-path>"}: ${error.message}`
);
}
: undefined,
});
server
端定義好後,接下來就來介紹 clinet
端怎麼呼叫~,用法很簡單只要引用 AppRouter
到 createTRPCNext function
中就好了,這邊的 createTRPCNext
是封裝 api request
結果。
transformer
: de-serialization data to server。loggerLink
: log
httpBatchLink
: 整合 request
結果例如 url 或是 header
import { type AppRouter } from "~/server/api/root";
export const api = createTRPCNext<AppRouter>({
config() {
return {
transformer: superjson,
links: [
loggerLink({
enabled: (opts) =>
process.env.NODE_ENV === "development" ||
(opts.direction === "down" && opts.result instanceof Error),
}),
httpBatchLink({
url: `${process.env.your_base_url}/api/trpc`,
headers: () => ({
Authorization: `Bearer ${your_token}`
})
}),
],
};
},
// client 端 call api 結果是否要在 server side render 呼叫 client 端做 cache
ssr: false,
});
是不是跟 react query
用起來很像呢,沒錯!!!trpc client
端就是封裝 react query
,所以所有 react query
的概念這邊都可以通用,看到這邊是不是要好好學好 react query
了呢 XD
const SomeComponent = ()=>{
const { data, isLoading, isError } = api.example.hello.useQuery({ text: "from tRPC" });
if(isLoading) return 'Loading...'
if(isError) return 'Error!!'
return (
<>{data.greeting}</> // Hello from tRPC
)
}
最後謝謝大家耐心看完今天內容,總共花了三天才開始進到 code
,畢竟要把 trpc
這個概念解釋清楚一時間很難說的很完整,如果內容有錯誤或是不懂地方還請大家指正~如果有任何想法可以底下留言讓我知道喔XD
✅ 前端社群 :
https://lihi3.cc/kBe0Y
httpBatchLink : 整合 request 結果例如 url 或是 he`ader
這裡的 header 好像誤加了一個符號 !?
好嚴格的 code review